#!/usr/bin/env python
# Licensed to Cloudera, Inc. under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  Cloudera, Inc. licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import json
import logging
import unittest
from unittest.mock import Mock, patch

import pytest
from django.test import TestCase

from desktop import conf
from desktop.lib.botserver.views import *
from desktop.lib.django_test_util import make_logged_in_client
from desktop.models import Document2, _get_gist_document
from useradmin.models import User

LOG = logging.getLogger()


class TestBotServer(TestCase):
  @classmethod
  def setup_class(cls):
    if not conf.SLACK.IS_ENABLED.get():
      pytest.skip("Skipping Test")

    # Slack user: test
    cls.client = make_logged_in_client(username="test", groupname="default", recreate=True, is_superuser=False)
    cls.user = User.objects.get(username="test")

    # Other slack user: test_not_me
    cls.client_not_me = make_logged_in_client(username="test_not_me", groupname="default", recreate=True, is_superuser=False)
    cls.user_not_me = User.objects.get(username="test_not_me")

  def setup_method(self):
    self.host_domain = 'testserver.gethue.com'
    self.is_http_secure = True  # https if true else http

    self.email_domain = '.'.join(self.host_domain.split('.')[-2:])

    self.channel_id = "channel"
    self.message_ts = "12.1"
    self.user_id = "user_id"

  def test_handle_on_message(self):
    with patch('desktop.lib.botserver.views._send_message') as _send_message:
      with patch('desktop.lib.botserver.views.handle_select_statement') as handle_select_statement:
        bot_id = "bot_id"
        message_element = [{'elements': [{'text': 'hello hue test'}]}]

        # Bot sending message
        response = handle_on_message(
          self.host_domain, self.is_http_secure, self.channel_id, bot_id, message_element, self.user_id, self.message_ts
        )
        assert response.status_code == 200
        assert not _send_message.called

        help_block = [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": (
                "Hey <@user_id>, I'm your SQL Assistant! "
                "I'm here to assist users with their SQL queries and *<https://docs.gethue.com/user/concept/#slack|much more.>*\n"
                "Here are the few things I can help you with:"
              ),
            },
          },
          {"type": "divider"},
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Share query/gist links* in channel which unfurls in a rich preview, showing query details and result in message thread if available.",  # noqa: E501
            },
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "Create a gist for your query, select the channel and *share directly from the Hue Editor* window.",
            },
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Detect SQL SELECT statements* in the channel and suggests a gist link for improved query discussions.",
            },
          },
          {
            "type": "section",
            "text": {"type": "mrkdwn", "text": "Type `@Hue queries` to explore a list of important queries from the latest *query bank*."},
          },
        ]

        # Help message
        handle_on_message(self.host_domain, self.is_http_secure, self.channel_id, None, message_element, self.user_id, self.message_ts)
        _send_message.assert_called_with(self.channel_id, block_element=help_block)

        # Detect SQL
        message_element = [
          {
            'elements': [
              {
                'text': 'Hi Team, need help with query',
              }
            ],
          },
          {
            'elements': [
              {
                'text': 'SELECT 1',
              }
            ],
          },
        ]

        handle_on_message(self.host_domain, self.is_http_secure, self.channel_id, None, message_element, self.user_id, self.message_ts)
        handle_select_statement.assert_called_with(
          self.host_domain, self.is_http_secure, self.channel_id, self.user_id, 'select 1', self.message_ts
        )

  def test_handle_select_statement(self):
    with patch('desktop.lib.botserver.views.check_slack_user_permission') as check_slack_user_permission:
      with patch('desktop.lib.botserver.views._make_select_statement_gist') as _make_select_statement_gist:
        with patch('desktop.lib.botserver.views._send_message') as _send_message:
          with patch('desktop.lib.botserver.views.get_user') as get_user:
            statement = 'select 1'
            detect_msg = (
              'Hi <@user_id> \n Looks like you are copy/pasting SQL, instead now you can send Editor links which unfurls in a rich preview!'
            )

            # For Slack user not Hue user
            get_user.side_effect = SlackBotException('Slack user does not have access to the query')

            with pytest.raises(SlackBotException):
              handle_select_statement(self.host_domain, self.is_http_secure, self.channel_id, self.user_id, statement, self.message_ts)
            _send_message.assert_called_with('channel', message=detect_msg)
            assert not _make_select_statement_gist.called

            # For Slack user is Hue user
            get_user.side_effect = None
            get_user.return_value = self.user

            handle_select_statement(self.host_domain, self.is_http_secure, self.channel_id, self.user_id, statement, self.message_ts)
            _send_message.assert_called_with('channel', message=detect_msg)
            _make_select_statement_gist.assert_called_with(self.host_domain, self.is_http_secure, self.user, 'channel', 'select 1')

  def test_handle_query_history_link(self):
    with patch('desktop.lib.botserver.views.slack_client.chat_unfurl') as chat_unfurl:
      with patch('desktop.lib.botserver.views._check_status') as check_status:
        with patch('desktop.lib.botserver.views.send_result_file') as send_result_file:
          with patch('desktop.lib.botserver.views.slack_client.users_info') as users_info:
            with patch('desktop.lib.botserver.views._query_result') as query_result:
              with patch('desktop.lib.botserver.views._make_result_table') as result_table:
                with patch('desktop.lib.botserver.views._send_message') as _send_message:
                  doc_data = {
                    "dialect": "mysql",
                    "snippets": [
                      {
                        "statement_raw": "SELECT 5000",
                      }
                    ],
                    'uuid': 'doc uuid',
                  }
                  doc = Document2.objects.create(data=json.dumps(doc_data), owner=self.user)
                  links = [{"url": "https://{host_domain}/hue/editor?editor=".format(host_domain=self.host_domain) + str(doc.id)}]

                  # Slack user is Hue user but without read access sends link
                  users_info.return_value = {
                    "ok": True,
                    "user": {"is_bot": False, "profile": {"email": "test_not_me@{domain}".format(domain=self.email_domain)}},
                  }
                  with pytest.raises(PopupException):
                    handle_on_link_shared(self.host_domain, "channel", "12.1", links, "<@user_id>")

                  # Slack user is Hue user with read access sends link
                  doc.update_permission(self.user, is_link_on=True)

                  check_status.return_value = {'query_status': {'status': 'available'}, 'status': 0}
                  query_result.return_value = {
                    'data': [[5000]],
                    'meta': [{'comment': '', 'name': '5000', 'type': 'INT_TYPE'}],
                  }
                  result_table.return_value = '  Columns(1)\n------------  ----\n        5000  5000'

                  handle_on_link_shared(self.host_domain, self.channel_id, self.message_ts, links, self.user_id)

                  query_preview = {
                    links[0]['url']: {
                      "color": "#025BA6",
                      "blocks": [
                        {
                          "type": "section",
                          "text": {
                            "type": "mrkdwn",
                            "text": "\n*<{url}|Open  query of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
                          },
                        },
                        {
                          "type": "divider",
                        },
                        {
                          "type": "section",
                          "text": {
                            "type": "mrkdwn",
                            "text": "*Statement:*\n```SELECT 5000```",
                          },
                        },
                        {
                          'type': 'section',
                          'text': {
                            'type': 'mrkdwn',
                            'text': "*Query result:*\n```  Columns(1)\n------------  ----\n        5000  5000```",
                          },
                        },
                      ],
                    }
                  }

                  chat_unfurl.assert_called_with(channel=self.channel_id, ts=self.message_ts, unfurls=query_preview)
                  assert send_result_file.called

                  # Document does not exist
                  qhistory_url = "https://{host_domain}/hue/editor?editor=109644".format(host_domain=self.host_domain)
                  with pytest.raises(SlackBotException):
                    handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": qhistory_url}], "<@user_id>")
                  _send_message.assert_called_with('channel', message='Query document not found or does not exist.', message_ts='12.1')

                  # Cannot unfurl link with invalid query link
                  inv_qhistory_url = "https://{host_domain}/hue/editor/?type=4".format(host_domain=self.host_domain)
                  with pytest.raises(SlackBotException):
                    handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": inv_qhistory_url}], "<@user_id>")
                  _send_message.assert_called_with(
                    'channel', message='Could not access the query, please check the link again.', message_ts='12.1'
                  )

  def test_handle_gist_link(self):
    with patch('desktop.lib.botserver.views.slack_client.chat_unfurl') as chat_unfurl:
      with patch('desktop.lib.botserver.views.slack_client.users_info') as users_info:
        with patch('desktop.lib.botserver.views.send_result_file') as send_result_file:
          with patch('desktop.lib.botserver.views._send_message') as _send_message:
            doc_data = {"statement_raw": "SELECT 98765"}
            gist_doc = Document2.objects.create(name='Mysql Query', type='gist', owner=self.user, data=json.dumps(doc_data), extra='mysql')
            links = [{"url": "https://{host_domain}/hue/gist?uuid=".format(host_domain=self.host_domain) + gist_doc.uuid}]

            # Slack user who is Hue user sends link
            users_info.return_value = {
              "ok": True,
              "user": {"is_bot": False, "profile": {"email": "test@{domain}".format(domain=self.email_domain)}},
            }
            handle_on_link_shared(self.host_domain, self.channel_id, self.message_ts, links, self.user_id)

            gist_preview = {
              links[0]['url']: {
                "color": "#025BA6",
                "blocks": [
                  {
                    "type": "section",
                    "text": {
                      "type": "mrkdwn",
                      "text": "\n*<{url}|Open Mysql Query gist of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
                    },
                  },
                  {
                    "type": "divider",
                  },
                  {
                    "type": "section",
                    "text": {
                      "type": "mrkdwn",
                      "text": "*Statement:*\n```SELECT 98765```",
                    },
                  },
                ],
              }
            }

            chat_unfurl.assert_called_with(channel=self.channel_id, ts=self.message_ts, unfurls=gist_preview)
            assert not send_result_file.called

            # Gist link sent directly from Hue to Slack via bot
            users_info.return_value = {
              "ok": True,
              "user": {
                "is_bot": True,
              },
            }
            handle_on_link_shared(self.host_domain, self.channel_id, self.message_ts, links, self.user_id)

            chat_unfurl.assert_called_with(channel=self.channel_id, ts=self.message_ts, unfurls=gist_preview)
            assert not send_result_file.called

            # Gist document does not exist
            gist_url = "https://{host_domain}/hue/gist?uuid=6d1c407b-d999-4dfd-ad23-d3a46c19a427".format(host_domain=self.host_domain)
            with pytest.raises(SlackBotException):
              handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": gist_url}], "<@user_id>")
            _send_message.assert_called_with('channel', message='Query document not found or does not exist.', message_ts='12.1')

            # Cannot unfurl with invalid gist link
            inv_gist_url = "https://{host_domain}/hue/gist?uuids/=invalid_link".format(host_domain=self.host_domain)
            with pytest.raises(SlackBotException):
              handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": inv_gist_url}], "<@user_id>")
            _send_message.assert_called_with(
              'channel', message='Could not access the query, please check the link again.', message_ts='12.1'
            )

  def test_slack_user_not_hue_user(self):
    with patch('desktop.lib.botserver.views.slack_client.users_info') as users_info:
      with patch('desktop.lib.botserver.views._send_message') as _send_message:
        # Same domain but diff email prefix
        users_info.return_value = {
          "ok": True,
          "user": {"is_bot": False, "profile": {"email": "test_user_not_exist@{domain}".format(domain=self.email_domain)}},
        }
        slack_user = check_slack_user_permission(self.host_domain, self.user_id)

        with pytest.raises(SlackBotException):
          get_user("channel", slack_user, "12.1")
        _send_message.assert_called_with('channel', message='Corresponding Hue user not found or does not have access.', message_ts='12.1')

        # Different domain but same email prefix
        users_info.return_value = {"ok": True, "user": {"is_bot": False, "profile": {"email": "test@example.com"}}}
        slack_user = check_slack_user_permission(self.host_domain, self.user_id)

        with pytest.raises(SlackBotException):
          get_user("channel", slack_user, "12.1")
        _send_message.assert_called_with('channel', message='Corresponding Hue user not found or does not have access.', message_ts='12.1')

  def test_handle_on_app_mention(self):
    with patch('desktop.lib.botserver.views.check_slack_user_permission') as check_slack_user_permission:
      with patch('desktop.lib.botserver.views.get_user') as get_user:
        with patch('desktop.lib.botserver.views.handle_query_bank') as handle_query_bank:
          text = '@hue some message'
          handle_on_app_mention(self.host_domain, self.channel_id, self.user_id, text, self.message_ts)

          assert not handle_query_bank.called

          text = '@hue queries'
          handle_on_app_mention(self.host_domain, self.channel_id, self.user_id, text, self.message_ts)

          handle_query_bank.assert_called_with(self.channel_id, self.user_id)

  def test_handle_query_bank(self):
    with patch('desktop.lib.botserver.views.get_all_queries') as get_all_queries:
      with patch('desktop.lib.botserver.views._send_message') as _send_message:
        get_all_queries.return_value = [
          {"name": "Test Query 1", "data": {"query": {"statement": "SELECT 1"}}},
          {"name": "Test Query 2", "data": {"query": {"statement": "SELECT 2"}}},
        ]

        test_query_block = [
          {'type': 'section', 'text': {'type': 'mrkdwn', 'text': 'Hi <@user_id>, here is the list of all saved queries!'}},
          {'type': 'divider'},
          {'type': 'section', 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 1 \n *Statement:*\n ```SELECT 1```'}},
          {'type': 'section', 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 2 \n *Statement:*\n ```SELECT 2```'}},
        ]

        handle_query_bank(self.channel_id, self.user_id)
        _send_message.assert_called_with(self.channel_id, block_element=test_query_block)
